/*-------------------------------------------------------------------------
 *
 * ginentrypage.c
 *	  处理 GIN 条目树页面的例程。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *			src/backend/access/gin/ginentrypage.c
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/gin_private.h"
#include "access/ginxlog.h"
#include "access/xloginsert.h"
#include "miscadmin.h"
#include "utils/rel.h"

static void fc_entrySplitPage(GinBtree fc_btree, Buffer fc_origbuf,
						   GinBtreeStack *fc_stack,
						   GinBtreeEntryInsertData *fc_insertData,
						   BlockNumber fc_updateblkno,
						   Page *fc_newlpage, Page *fc_newrpage);

/*
 * 为条目树形成一个元组。
 *
 * 如果元组过大而无法存储，当errorTooBig为true时，该函数将抛出适当的错误，或者当errorTooBig为false时返回NULL。
 *
 * 请参见src/backend/access/gin/README，了解此处构建的索引元组格式。我们基于假设正在创建一个包含nipd项目的发布列表的叶级键条目。
 * 如果调用者实际上是试图制作一个发布树条目、非叶条目或待处理列表条目，则应传入dataSize = 0，然后根据需要覆盖t_tid字段。
 * 在任何情况下，'data'可以为空，以跳过填充发布列表；如果data = NULL且nipd > 0，调用者负责之后填充它。
 */
IndexTuple GinFormTuple(GinState *fc_ginstate,
			 OffsetNumber fc_attnum, Datum fc_key, GinNullCategory fc_category,
			 Pointer fc_data, Size fc_dataSize, int fc_nipd,
			 bool fc_errorTooBig)
{
	Datum		fc_datums[2];
	bool		fc_isnull[2];
	IndexTuple	fc_itup;
	uint32		fc_newsize;

	/* 构建基本元组：可选列数，加上键数据 */
	if (fc_ginstate->oneCol)
	{
		fc_datums[0] = fc_key;
		fc_isnull[0] = (fc_category != GIN_CAT_NORM_KEY);
	}
	else
	{
		fc_datums[0] = UInt16GetDatum(fc_attnum);
		fc_isnull[0] = false;
		fc_datums[1] = fc_key;
		fc_isnull[1] = (fc_category != GIN_CAT_NORM_KEY);
	}

	fc_itup = index_form_tuple(fc_ginstate->tupdesc[fc_attnum - 1], fc_datums, fc_isnull);

	/*
	 * 确定并存储发布列表的偏移量，确保如果需要的话有足够的空间容纳类别字节。
	 *
	 * 注意：因为index_form_tuple对元组大小进行了MAXALIGN处理，所以可能会有一些浪费的填充空间。重新计算数据长度以防止这种情况是否值得？这也允许我们确保真实数据不与GinNullCategory字节重叠，而此代码目前对此是抱有信心的。
	 */
	fc_newsize = IndexTupleSize(fc_itup);

	if (IndexTupleHasNulls(fc_itup))
	{
		uint32		fc_minsize;

		Assert(fc_category != GIN_CAT_NORM_KEY);
		fc_minsize = GinCategoryOffset(fc_itup, fc_ginstate) + sizeof(GinNullCategory);
		fc_newsize = Max(fc_newsize, fc_minsize);
	}

	fc_newsize = SHORTALIGN(fc_newsize);

	GinSetPostingOffset(fc_itup, fc_newsize);
	GinSetNPosting(fc_itup, fc_nipd);

	/*
	 * 如果需要，添加发布列表所需的空间。然后检查元组是否过大而无法存储。
	 */
	fc_newsize += fc_dataSize;

	fc_newsize = MAXALIGN(fc_newsize);

	if (fc_newsize > GinMaxItemSize)
	{
		if (fc_errorTooBig)
			ereport(ERROR,
					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
					 errmsg("index row size %zu exceeds maximum %zu for index \"%s\"",
							(Size) fc_newsize, (Size) GinMaxItemSize,
							RelationGetRelationName(fc_ginstate->index))));
		pfree(fc_itup);
		return NULL;
	}

	/*
	 * 如果需要，调整元组大小
	 */
	if (fc_newsize != IndexTupleSize(fc_itup))
	{
		fc_itup = repalloc(fc_itup, fc_newsize);

		/*
		 * PostgreSQL 9.3及之前版本没有清除这个新空间，因此我们
		 * 在从磁盘读取元组时可能会发现未初始化的填充。
		 */
		memset((char *) fc_itup + IndexTupleSize(fc_itup),
			   0, fc_newsize - IndexTupleSize(fc_itup));
		/* 在元组头中设置新大小 */
		fc_itup->t_info &= ~INDEX_SIZE_MASK;
		fc_itup->t_info |= fc_newsize;
	}

	/*
	 * 如果提供了发布列表，则复制到其中
	 */
	if (fc_data)
	{
		char	   *fc_ptr = GinGetPosting(fc_itup);

		memcpy(fc_ptr, fc_data, fc_dataSize);
	}

	/*
	 * 如果需要，插入类别字节
	 */
	if (fc_category != GIN_CAT_NORM_KEY)
	{
		Assert(IndexTupleHasNulls(fc_itup));
		GinSetNullCategory(fc_itup, fc_ginstate, fc_category);
	}
	return fc_itup;
}

/*
 * 从叶条目元组读取项目指针。
 *
 * 返回一个palloc'd ItemPointers数组。项目的数量存储在*nitems中。
 */
ItemPointer ginReadTuple(GinState *fc_ginstate, OffsetNumber fc_attnum, IndexTuple fc_itup,
			 int *fc_nitems)
{
	Pointer		fc_ptr = GinGetPosting(fc_itup);
	int			fc_nipd = GinGetNPosting(fc_itup);
	ItemPointer fc_ipd;
	int			fc_ndecoded;

	if (GinItupIsCompressed(fc_itup))
	{
		if (fc_nipd > 0)
		{
			fc_ipd = ginPostingListDecode((GinPostingList *) fc_ptr, &fc_ndecoded);
			if (fc_nipd != fc_ndecoded)
				elog(ERROR, "number of items mismatch in GIN entry tuple, %d in tuple header, %d decoded",
					 fc_nipd, fc_ndecoded);
		}
		else
		{
			fc_ipd = palloc(0);
		}
	}
	else
	{
		fc_ipd = (ItemPointer) palloc(sizeof(ItemPointerData) * fc_nipd);
		memcpy(fc_ipd, fc_ptr, sizeof(ItemPointerData) * fc_nipd);
	}
	*fc_nitems = fc_nipd;
	return fc_ipd;
}

/*
 * 通过从给定的元组复制键数据来形成非叶条目元组，
 * 它可以是叶条目或非叶条目元组。
 *
 * 源元组中的任何发布列表都不会被复制。指定的子块编号将插入到t_tid中。
 */
static IndexTuple fc_GinFormInteriorTuple(IndexTuple fc_itup, Page fc_page, BlockNumber fc_childblk)
{
	IndexTuple	fc_nitup;

	if (GinPageIsLeaf(fc_page) && !GinIsPostingTree(fc_itup))
	{
		/* 元组包含一个发布列表，仅复制该之前的内容 */
		uint32		fc_origsize = GinGetPostingOffset(fc_itup);

		fc_origsize = MAXALIGN(fc_origsize);
		fc_nitup = (IndexTuple) palloc(fc_origsize);
		memcpy(fc_nitup, fc_itup, fc_origsize);
		/* ... 确保修复大小头字段 ... */
		fc_nitup->t_info &= ~INDEX_SIZE_MASK;
		fc_nitup->t_info |= fc_origsize;
	}
	else
	{
		/* 按原样复制元组 */
		fc_nitup = (IndexTuple) palloc(IndexTupleSize(fc_itup));
		memcpy(fc_nitup, fc_itup, IndexTupleSize(fc_itup));
	}

	/* 现在插入正确的下行链接 */
	GinSetDownlink(fc_nitup, fc_childblk);

	return fc_nitup;
}

/*
 * 条目树是“静态”的，即元组永远不会从中删除，
 * 所以我们不使用右边界，我们使用最右侧的键。
 */
static IndexTuple fc_getRightMostTuple(Page fc_page)
{
	OffsetNumber fc_maxoff = PageGetMaxOffsetNumber(fc_page);

	return (IndexTuple) PageGetItem(fc_page, PageGetItemId(fc_page, fc_maxoff));
}

static bool fc_entryIsMoveRight(GinBtree fc_btree, Page fc_page)
{
	IndexTuple	fc_itup;
	OffsetNumber fc_attnum;
	Datum		fc_key;
	GinNullCategory fc_category;

	if (GinPageRightMost(fc_page))
		return false;

	fc_itup = fc_getRightMostTuple(fc_page);
	fc_attnum = gintuple_get_attrnum(fc_btree->ginstate, fc_itup);
	fc_key = gintuple_get_key(fc_btree->ginstate, fc_itup, &fc_category);

	if (ginCompareAttEntries(fc_btree->ginstate,
							 fc_btree->entryAttnum, fc_btree->entryKey, fc_btree->entryCategory,
							 fc_attnum, fc_key, fc_category) > 0)
		return true;

	return false;
}

/*
 * 在非叶页面中查找正确的元组。假设
 * 页面已正确选择并且搜索值应在页面上
 */
static BlockNumber fc_entryLocateEntry(GinBtree fc_btree, GinBtreeStack *fc_stack)
{
	OffsetNumber fc_low,
				fc_high,
				fc_maxoff;
	IndexTuple	fc_itup = NULL;
	int			fc_result;
	Page		fc_page = BufferGetPage(fc_stack->buffer);

	Assert(!GinPageIsLeaf(fc_page));
	Assert(!GinPageIsData(fc_page));

	if (fc_btree->fullScan)
	{
		fc_stack->off = FirstOffsetNumber;
		fc_stack->predictNumber *= PageGetMaxOffsetNumber(fc_page);
		return fc_btree->getLeftMostChild(fc_btree, fc_page);
	}

	fc_low = FirstOffsetNumber;
	fc_maxoff = fc_high = PageGetMaxOffsetNumber(fc_page);
	Assert(fc_high >= fc_low);

	fc_high++;

	while (fc_high > fc_low)
	{
		OffsetNumber fc_mid = fc_low + ((fc_high - fc_low) / 2);

		if (fc_mid == fc_maxoff && GinPageRightMost(fc_page))
		{
			/* 右无穷大 */
			fc_result = -1;
		}
		else
		{
			OffsetNumber fc_attnum;
			Datum		fc_key;
			GinNullCategory fc_category;

			fc_itup = (IndexTuple) PageGetItem(fc_page, PageGetItemId(fc_page, fc_mid));
			fc_attnum = gintuple_get_attrnum(fc_btree->ginstate, fc_itup);
			fc_key = gintuple_get_key(fc_btree->ginstate, fc_itup, &fc_category);
			fc_result = ginCompareAttEntries(fc_btree->ginstate,
										  fc_btree->entryAttnum,
										  fc_btree->entryKey,
										  fc_btree->entryCategory,
										  fc_attnum, fc_key, fc_category);
		}

		if (fc_result == 0)
		{
			fc_stack->off = fc_mid;
			Assert(GinGetDownlink(fc_itup) != GIN_ROOT_BLKNO);
			return GinGetDownlink(fc_itup);
		}
		else if (fc_result > 0)
			fc_low = fc_mid + 1;
		else
			fc_high = fc_mid;
	}

	Assert(fc_high >= FirstOffsetNumber && fc_high <= fc_maxoff);

	fc_stack->off = fc_high;
	fc_itup = (IndexTuple) PageGetItem(fc_page, PageGetItemId(fc_page, fc_high));
	Assert(GinGetDownlink(fc_itup) != GIN_ROOT_BLKNO);
	return GinGetDownlink(fc_itup);
}

/*
 * 在叶页面上搜索值的正确位置。
 * 页面应正确选择。
 * 如果在页面上找到值则返回true。
 */
static bool fc_entryLocateLeafEntry(GinBtree fc_btree, GinBtreeStack *fc_stack)
{
	Page		fc_page = BufferGetPage(fc_stack->buffer);
	OffsetNumber fc_low,
				fc_high;

	Assert(GinPageIsLeaf(fc_page));
	Assert(!GinPageIsData(fc_page));

	if (fc_btree->fullScan)
	{
		fc_stack->off = FirstOffsetNumber;
		return true;
	}

	fc_low = FirstOffsetNumber;
	fc_high = PageGetMaxOffsetNumber(fc_page);

	if (fc_high < fc_low)
	{
		fc_stack->off = FirstOffsetNumber;
		return false;
	}

	fc_high++;

	while (fc_high > fc_low)
	{
		OffsetNumber fc_mid = fc_low + ((fc_high - fc_low) / 2);
		IndexTuple	fc_itup;
		OffsetNumber fc_attnum;
		Datum		fc_key;
		GinNullCategory fc_category;
		int			fc_result;

		fc_itup = (IndexTuple) PageGetItem(fc_page, PageGetItemId(fc_page, fc_mid));
		fc_attnum = gintuple_get_attrnum(fc_btree->ginstate, fc_itup);
		fc_key = gintuple_get_key(fc_btree->ginstate, fc_itup, &fc_category);
		fc_result = ginCompareAttEntries(fc_btree->ginstate,
									  fc_btree->entryAttnum,
									  fc_btree->entryKey,
									  fc_btree->entryCategory,
									  fc_attnum, fc_key, fc_category);
		if (fc_result == 0)
		{
			fc_stack->off = fc_mid;
			return true;
		}
		else if (fc_result > 0)
			fc_low = fc_mid + 1;
		else
			fc_high = fc_mid;
	}

	fc_stack->off = fc_high;
	return false;
}

static OffsetNumber fc_entryFindChildPtr(GinBtree fc_btree, Page fc_page, BlockNumber fc_blkno, OffsetNumber fc_storedOff)
{
	OffsetNumber fc_i,
				fc_maxoff = PageGetMaxOffsetNumber(fc_page);
	IndexTuple	fc_itup;

	Assert(!GinPageIsLeaf(fc_page));
	Assert(!GinPageIsData(fc_page));

	/* 如果页面未更改，则返回storedOff */
	if (fc_storedOff >= FirstOffsetNumber && fc_storedOff <= fc_maxoff)
	{
		fc_itup = (IndexTuple) PageGetItem(fc_page, PageGetItemId(fc_page, fc_storedOff));
		if (GinGetDownlink(fc_itup) == fc_blkno)
			return fc_storedOff;

		/*
		 * 我们希望所需指针向右移动。如果没有删除，这是真的。
		 */
		for (fc_i = fc_storedOff + 1; fc_i <= fc_maxoff; fc_i++)
		{
			fc_itup = (IndexTuple) PageGetItem(fc_page, PageGetItemId(fc_page, fc_i));
			if (GinGetDownlink(fc_itup) == fc_blkno)
				return fc_i;
		}
		fc_maxoff = fc_storedOff - 1;
	}

	/* 最后机会 */
	for (fc_i = FirstOffsetNumber; fc_i <= fc_maxoff; fc_i++)
	{
		fc_itup = (IndexTuple) PageGetItem(fc_page, PageGetItemId(fc_page, fc_i));
		if (GinGetDownlink(fc_itup) == fc_blkno)
			return fc_i;
	}

	return InvalidOffsetNumber;
}

static BlockNumber fc_entryGetLeftMostPage(GinBtree fc_btree, Page fc_page)
{
	IndexTuple	fc_itup;

	Assert(!GinPageIsLeaf(fc_page));
	Assert(!GinPageIsData(fc_page));
	Assert(PageGetMaxOffsetNumber(fc_page) >= FirstOffsetNumber);

	fc_itup = (IndexTuple) PageGetItem(fc_page, PageGetItemId(fc_page, FirstOffsetNumber));
	return GinGetDownlink(fc_itup);
}

static bool fc_entryIsEnoughSpace(GinBtree fc_btree, Buffer fc_buf, OffsetNumber fc_off,
				   GinBtreeEntryInsertData *fc_insertData)
{
	Size		fc_releasedsz = 0;
	Size		fc_addedsz;
	Page		fc_page = BufferGetPage(fc_buf);

	Assert(fc_insertData->entry);
	Assert(!GinPageIsData(fc_page));

	if (fc_insertData->isDelete)
	{
		IndexTuple	fc_itup = (IndexTuple) PageGetItem(fc_page, PageGetItemId(fc_page, fc_off));

		fc_releasedsz = MAXALIGN(IndexTupleSize(fc_itup)) + sizeof(ItemIdData);
	}

	fc_addedsz = MAXALIGN(IndexTupleSize(fc_insertData->entry)) + sizeof(ItemIdData);

	if (PageGetFreeSpace(fc_page) + fc_releasedsz >= fc_addedsz)
		return true;

	return false;
}

/*
 * 如果元组存在并且我们应该更新它，在叶页面上删除元组，
 * 如果发生了子块拆分，将旧子块号更新为新右页面。
 */
static void fc_entryPreparePage(GinBtree fc_btree, Page fc_page, OffsetNumber fc_off,
				 GinBtreeEntryInsertData *fc_insertData, BlockNumber fc_updateblkno)
{
	Assert(fc_insertData->entry);
	Assert(!GinPageIsData(fc_page));

	if (fc_insertData->isDelete)
	{
		Assert(GinPageIsLeaf(fc_page));
		PageIndexTupleDelete(fc_page, fc_off);
	}

	if (!GinPageIsLeaf(fc_page) && fc_updateblkno != InvalidBlockNumber)
	{
		IndexTuple	fc_itup = (IndexTuple) PageGetItem(fc_page, PageGetItemId(fc_page, fc_off));

		GinSetDownlink(fc_itup, fc_updateblkno);
	}
}

/*
 * 准备在条目页面上插入数据。
 *
 * 如果可以容纳，返回 GPTP_INSERT，并在进入插入临界区之前完成所需的任何设置。
 * *ptp_workspace 可以设置为传递信息到 execPlaceToPage 函数。
 *
 * 如果无法容纳，则执行页面拆分并返回两个临时页面
 * 图像到 *newlpage 和 *newrpage，结果为 GPTP_SPLIT。
 *
 * 在这两种情况下，都不应在此处修改给定的页面缓冲区。
 *
 * 注意：在插入到内部节点时，除了插入给定
 * 项目外，栈中现有项目的下链也将更新为
 * 指向 updateblkno。
 */
static GinPlaceToPageRC fc_entryBeginPlaceToPage(GinBtree fc_btree, Buffer fc_buf, GinBtreeStack *fc_stack,
					  void *fc_insertPayload, BlockNumber fc_updateblkno,
					  void **fc_ptp_workspace,
					  Page *fc_newlpage, Page *fc_newrpage)
{
	GinBtreeEntryInsertData *fc_insertData = fc_insertPayload;
	OffsetNumber fc_off = fc_stack->off;

	/* 如果不能容纳，处理拆分情况 */
	if (!fc_entryIsEnoughSpace(fc_btree, fc_buf, fc_off, fc_insertData))
	{
		fc_entrySplitPage(fc_btree, fc_buf, fc_stack, fc_insertData, fc_updateblkno,
					   fc_newlpage, fc_newrpage);
		return GPTP_SPLIT;
	}

	/* 否则，我们准备继续插入 */
	return GPTP_INSERT;
}

/*
 * 在 beginPlaceToPage 决定它会适合后执行数据插入。
 *
 * 这是在关键部分内部调用的，XLOG 记录创建（如果需要）已经开始。目标缓冲区在槽 0 中注册。
 */
static void fc_entryExecPlaceToPage(GinBtree fc_btree, Buffer fc_buf, GinBtreeStack *fc_stack,
					 void *fc_insertPayload, BlockNumber fc_updateblkno,
					 void *fc_ptp_workspace)
{
	GinBtreeEntryInsertData *fc_insertData = fc_insertPayload;
	Page		fc_page = BufferGetPage(fc_buf);
	OffsetNumber fc_off = fc_stack->off;
	OffsetNumber fc_placed;

	fc_entryPreparePage(fc_btree, fc_page, fc_off, fc_insertData, fc_updateblkno);

	fc_placed = PageAddItem(fc_page,
						 (Item) fc_insertData->entry,
						 IndexTupleSize(fc_insertData->entry),
						 fc_off, false, false);
	if (fc_placed != fc_off)
		elog(ERROR, "failed to add item to index page in \"%s\"",
			 RelationGetRelationName(fc_btree->index));

	if (RelationNeedsWAL(fc_btree->index) && !fc_btree->isBuild)
	{
		/*
		 * 这必须是静态的，因为它必须在 XLogInsert 之前存活，
		 * 而我们不能在此调用 palloc。虽然不太优雅，但 XLogInsert 的基础设施
		 * 本来就不是可重入的。
		 */
		static ginxlogInsertEntry fc_data;

		fc_data.isDelete = fc_insertData->isDelete;
		fc_data.offset = fc_off;

		XLogRegisterBufData(0, (char *) &fc_data,
							offsetof(ginxlogInsertEntry, tuple));
		XLogRegisterBufData(0, (char *) fc_insertData->entry,
							IndexTupleSize(fc_insertData->entry));
	}
}

/*
 * 拆分条目页面并插入新数据。
 *
 * 将新的临时页面返回到 *newlpage 和 *newrpage。
 * 原始缓冲区保持不变。
 */
static void fc_entrySplitPage(GinBtree fc_btree, Buffer fc_origbuf,
			   GinBtreeStack *fc_stack,
			   GinBtreeEntryInsertData *fc_insertData,
			   BlockNumber fc_updateblkno,
			   Page *fc_newlpage, Page *fc_newrpage)
{
	OffsetNumber fc_off = fc_stack->off;
	OffsetNumber fc_i,
				fc_maxoff,
				fc_separator = InvalidOffsetNumber;
	Size		fc_totalsize = 0;
	Size		fc_lsize = 0,
				fc_size;
	char	   *fc_ptr;
	IndexTuple	fc_itup;
	Page		fc_page;
	Page		fc_lpage = PageGetTempPageCopy(BufferGetPage(fc_origbuf));
	Page		fc_rpage = PageGetTempPageCopy(BufferGetPage(fc_origbuf));
	Size		fc_pageSize = PageGetPageSize(fc_lpage);
	PGAlignedBlock fc_tupstore[2]; /* 可能需要两个页面的元组 */

	fc_entryPreparePage(fc_btree, fc_lpage, fc_off, fc_insertData, fc_updateblkno);

	/*
	 * 首先，将所有现有的元组和我们正在插入的新元组
	 * 一个接一个地追加到临时工作区。
	 */
	fc_maxoff = PageGetMaxOffsetNumber(fc_lpage);
	fc_ptr = fc_tupstore[0].data;
	for (fc_i = FirstOffsetNumber; fc_i <= fc_maxoff; fc_i++)
	{
		if (fc_i == fc_off)
		{
			fc_size = MAXALIGN(IndexTupleSize(fc_insertData->entry));
			memcpy(fc_ptr, fc_insertData->entry, fc_size);
			fc_ptr += fc_size;
			fc_totalsize += fc_size + sizeof(ItemIdData);
		}

		fc_itup = (IndexTuple) PageGetItem(fc_lpage, PageGetItemId(fc_lpage, fc_i));
		fc_size = MAXALIGN(IndexTupleSize(fc_itup));
		memcpy(fc_ptr, fc_itup, fc_size);
		fc_ptr += fc_size;
		fc_totalsize += fc_size + sizeof(ItemIdData);
	}

	if (fc_off == fc_maxoff + 1)
	{
		fc_size = MAXALIGN(IndexTupleSize(fc_insertData->entry));
		memcpy(fc_ptr, fc_insertData->entry, fc_size);
		fc_ptr += fc_size;
		fc_totalsize += fc_size + sizeof(ItemIdData);
	}

	/*
	 * 初始化左页面和右页面，并将所有元组复制回
	 * 它们。
	 */
	GinInitPage(fc_rpage, GinPageGetOpaque(fc_lpage)->flags, fc_pageSize);
	GinInitPage(fc_lpage, GinPageGetOpaque(fc_rpage)->flags, fc_pageSize);

	fc_ptr = fc_tupstore[0].data;
	fc_maxoff++;
	fc_lsize = 0;

	fc_page = fc_lpage;
	for (fc_i = FirstOffsetNumber; fc_i <= fc_maxoff; fc_i++)
	{
		fc_itup = (IndexTuple) fc_ptr;

		/*
		 * 决定拆分的位置。我们尝试使页面的总数据
		 * 大小相等，而不是元组数量。
		 */
		if (fc_lsize > fc_totalsize / 2)
		{
			if (fc_separator == InvalidOffsetNumber)
				fc_separator = fc_i - 1;
			fc_page = fc_rpage;
		}
		else
		{
			fc_lsize += MAXALIGN(IndexTupleSize(fc_itup)) + sizeof(ItemIdData);
		}

		if (PageAddItem(fc_page, (Item) fc_itup, IndexTupleSize(fc_itup), InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
			elog(ERROR, "failed to add item to index page in \"%s\"",
				 RelationGetRelationName(fc_btree->index));
		fc_ptr += MAXALIGN(IndexTupleSize(fc_itup));
	}

	/* 将临时页面返回给调用者 */
	*fc_newlpage = fc_lpage;
	*fc_newrpage = fc_rpage;
}

/*
 * 构造插入负载以插入给定缓冲区的下行链接。
 */
static void * fc_entryPrepareDownlink(GinBtree fc_btree, Buffer fc_lbuf)
{
	GinBtreeEntryInsertData *fc_insertData;
	Page		fc_lpage = BufferGetPage(fc_lbuf);
	BlockNumber fc_lblkno = BufferGetBlockNumber(fc_lbuf);
	IndexTuple	fc_itup;

	fc_itup = fc_getRightMostTuple(fc_lpage);

	fc_insertData = palloc(sizeof(GinBtreeEntryInsertData));
	fc_insertData->entry = fc_GinFormInteriorTuple(fc_itup, fc_lpage, fc_lblkno);
	fc_insertData->isDelete = false;

	return fc_insertData;
}

/*
 * 通过从子块获取最右值填充新根。
 * 也从 ginxlog 调用，不应该使用 btree。
 */
void ginEntryFillRoot(GinBtree fc_btree, Page fc_root,
				 BlockNumber fc_lblkno, Page fc_lpage,
				 BlockNumber fc_rblkno, Page fc_rpage)
{
	IndexTuple	fc_itup;

	fc_itup = fc_GinFormInteriorTuple(fc_getRightMostTuple(fc_lpage), fc_lpage, fc_lblkno);
	if (PageAddItem(fc_root, (Item) fc_itup, IndexTupleSize(fc_itup), InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
		elog(ERROR, "failed to add item to index root page");
	pfree(fc_itup);

	fc_itup = fc_GinFormInteriorTuple(fc_getRightMostTuple(fc_rpage), fc_rpage, fc_rblkno);
	if (PageAddItem(fc_root, (Item) fc_itup, IndexTupleSize(fc_itup), InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
		elog(ERROR, "failed to add item to index root page");
	pfree(fc_itup);
}

/*
 * 为条目页面访问设置 GinBtree
 *
 * 注意：在 WAL 恢复期间，ginstate 中可能没有有效数据
 * 除了一个伪造的 Relation 指针；关键数据也是虚假的。
 */
void ginPrepareEntryScan(GinBtree fc_btree, OffsetNumber fc_attnum,
					Datum fc_key, GinNullCategory fc_category,
					GinState *fc_ginstate)
{
	memset(fc_btree, 0, sizeof(GinBtreeData));

	fc_btree->index = fc_ginstate->index;
	fc_btree->rootBlkno = GIN_ROOT_BLKNO;
	fc_btree->ginstate = fc_ginstate;

	fc_btree->findChildPage = fc_entryLocateEntry;
	fc_btree->getLeftMostChild = fc_entryGetLeftMostPage;
	fc_btree->isMoveRight = fc_entryIsMoveRight;
	fc_btree->findItem = fc_entryLocateLeafEntry;
	fc_btree->findChildPtr = fc_entryFindChildPtr;
	fc_btree->beginPlaceToPage = fc_entryBeginPlaceToPage;
	fc_btree->execPlaceToPage = fc_entryExecPlaceToPage;
	fc_btree->fillRoot = ginEntryFillRoot;
	fc_btree->prepareDownlink = fc_entryPrepareDownlink;

	fc_btree->isData = false;
	fc_btree->fullScan = false;
	fc_btree->isBuild = false;

	fc_btree->entryAttnum = fc_attnum;
	fc_btree->entryKey = fc_key;
	fc_btree->entryCategory = fc_category;
}
